/*
 * Copyright (c) 2007, 2015, Oracle and/or its affiliates. All rights reserved.
 * ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
 */
/**
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements. See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership. The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License. You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied. See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */
package com.sun.org.apache.xml.internal.security.utils;

import java.io.IOException;
import java.io.StringReader;

public class RFC2253Parser {

  /**
   * Method rfc2253toXMLdsig
   *
   * @return normalized string
   */
  public static String rfc2253toXMLdsig(String dn) {
    // Transform from RFC1779 to RFC2253
    String normalized = normalize(dn, true);

    return rfctoXML(normalized);
  }

  /**
   * Method xmldsigtoRFC2253
   *
   * @return normalized string
   */
  public static String xmldsigtoRFC2253(String dn) {
    // Transform from RFC1779 to RFC2253
    String normalized = normalize(dn, false);

    return xmltoRFC(normalized);
  }

  /**
   * Method normalize
   *
   * @return normalized string
   */
  public static String normalize(String dn) {
    return normalize(dn, true);
  }

  /**
   * Method normalize
   *
   * @return normalized string
   */
  public static String normalize(String dn, boolean toXml) {
    //if empty string
    if ((dn == null) || dn.equals("")) {
      return "";
    }

    try {
      String DN = semicolonToComma(dn);
      StringBuilder sb = new StringBuilder();
      int i = 0;
      int l = 0;
      int k;

      //for name component
      for (int j = 0; (k = DN.indexOf(',', j)) >= 0; j = k + 1) {
        l += countQuotes(DN, j, k);

        if ((k > 0) && (DN.charAt(k - 1) != '\\') && (l % 2) == 0) {
          sb.append(parseRDN(DN.substring(i, k).trim(), toXml) + ",");

          i = k + 1;
          l = 0;
        }
      }

      sb.append(parseRDN(trim(DN.substring(i)), toXml));

      return sb.toString();
    } catch (IOException ex) {
      return dn;
    }
  }

  /**
   * Method parseRDN
   *
   * @return normalized string
   */
  static String parseRDN(String str, boolean toXml) throws IOException {
    StringBuilder sb = new StringBuilder();
    int i = 0;
    int l = 0;
    int k;

    for (int j = 0; (k = str.indexOf('+', j)) >= 0; j = k + 1) {
      l += countQuotes(str, j, k);

      if ((k > 0) && (str.charAt(k - 1) != '\\') && (l % 2) == 0) {
        sb.append(parseATAV(trim(str.substring(i, k)), toXml) + "+");

        i = k + 1;
        l = 0;
      }
    }

    sb.append(parseATAV(trim(str.substring(i)), toXml));

    return sb.toString();
  }

  /**
   * Method parseATAV
   *
   * @return normalized string
   */
  static String parseATAV(String str, boolean toXml) throws IOException {
    int i = str.indexOf('=');

    if ((i == -1) || ((i > 0) && (str.charAt(i - 1) == '\\'))) {
      return str;
    }
    String attrType = normalizeAT(str.substring(0, i));
    // only normalize if value is a String
    String attrValue = null;
    if (attrType.charAt(0) >= '0' && attrType.charAt(0) <= '9') {
      attrValue = str.substring(i + 1);
    } else {
      attrValue = normalizeV(str.substring(i + 1), toXml);
    }

    return attrType + "=" + attrValue;

  }

  /**
   * Method normalizeAT
   *
   * @return normalized string
   */
  static String normalizeAT(String str) {

    String at = str.toUpperCase().trim();

    if (at.startsWith("OID")) {
      at = at.substring(3);
    }

    return at;
  }

  /**
   * Method normalizeV
   *
   * @return normalized string
   */
  static String normalizeV(String str, boolean toXml) throws IOException {
    String value = trim(str);

    if (value.startsWith("\"")) {
      StringBuilder sb = new StringBuilder();
      StringReader sr = new StringReader(value.substring(1, value.length() - 1));
      int i = 0;
      char c;

      while ((i = sr.read()) > -1) {
        c = (char) i;

        //the following char is defined at 4.Relationship with RFC1779 and LDAPv2 inrfc2253
        if ((c == ',') || (c == '=') || (c == '+') || (c == '<')
            || (c == '>') || (c == '#') || (c == ';')) {
          sb.append('\\');
        }

        sb.append(c);
      }

      value = trim(sb.toString());
    }

    if (toXml) {
      if (value.startsWith("#")) {
        value = '\\' + value;
      }
    } else {
      if (value.startsWith("\\#")) {
        value = value.substring(1);
      }
    }

    return value;
  }

  /**
   * Method rfctoXML
   *
   * @return normalized string
   */
  static String rfctoXML(String string) {
    try {
      String s = changeLess32toXML(string);

      return changeWStoXML(s);
    } catch (Exception e) {
      return string;
    }
  }

  /**
   * Method xmltoRFC
   *
   * @return normalized string
   */
  static String xmltoRFC(String string) {
    try {
      String s = changeLess32toRFC(string);

      return changeWStoRFC(s);
    } catch (Exception e) {
      return string;
    }
  }

  /**
   * Method changeLess32toRFC
   *
   * @return normalized string
   */
  static String changeLess32toRFC(String string) throws IOException {
    StringBuilder sb = new StringBuilder();
    StringReader sr = new StringReader(string);
    int i = 0;
    char c;

    while ((i = sr.read()) > -1) {
      c = (char) i;

      if (c == '\\') {
        sb.append(c);

        char c1 = (char) sr.read();
        char c2 = (char) sr.read();

        //65 (A) 97 (a)
        if ((((c1 >= 48) && (c1 <= 57)) || ((c1 >= 65) && (c1 <= 70)) || ((c1 >= 97) && (c1
            <= 102)))
            && (((c2 >= 48) && (c2 <= 57))
            || ((c2 >= 65) && (c2 <= 70))
            || ((c2 >= 97) && (c2 <= 102)))) {
          char ch = (char) Byte.parseByte("" + c1 + c2, 16);

          sb.append(ch);
        } else {
          sb.append(c1);
          sb.append(c2);
        }
      } else {
        sb.append(c);
      }
    }

    return sb.toString();
  }

  /**
   * Method changeLess32toXML
   *
   * @return normalized string
   */
  static String changeLess32toXML(String string) throws IOException {
    StringBuilder sb = new StringBuilder();
    StringReader sr = new StringReader(string);
    int i = 0;

    while ((i = sr.read()) > -1) {
      if (i < 32) {
        sb.append('\\');
        sb.append(Integer.toHexString(i));
      } else {
        sb.append((char) i);
      }
    }

    return sb.toString();
  }

  /**
   * Method changeWStoXML
   *
   * @return normalized string
   */
  static String changeWStoXML(String string) throws IOException {
    StringBuilder sb = new StringBuilder();
    StringReader sr = new StringReader(string);
    int i = 0;
    char c;

    while ((i = sr.read()) > -1) {
      c = (char) i;

      if (c == '\\') {
        char c1 = (char) sr.read();

        if (c1 == ' ') {
          sb.append('\\');

          String s = "20";

          sb.append(s);
        } else {
          sb.append('\\');
          sb.append(c1);
        }
      } else {
        sb.append(c);
      }
    }

    return sb.toString();
  }

  /**
   * Method changeWStoRFC
   *
   * @return normalized string
   */
  static String changeWStoRFC(String string) {
    StringBuilder sb = new StringBuilder();
    int i = 0;
    int k;

    for (int j = 0; (k = string.indexOf("\\20", j)) >= 0; j = k + 3) {
      sb.append(trim(string.substring(i, k)) + "\\ ");

      i = k + 3;
    }

    sb.append(string.substring(i));

    return sb.toString();
  }

  /**
   * Method semicolonToComma
   *
   * @return normalized string
   */
  static String semicolonToComma(String str) {
    return removeWSandReplace(str, ";", ",");
  }

  /**
   * Method removeWhiteSpace
   *
   * @return normalized string
   */
  static String removeWhiteSpace(String str, String symbol) {
    return removeWSandReplace(str, symbol, symbol);
  }

  /**
   * Method removeWSandReplace
   *
   * @return normalized string
   */
  static String removeWSandReplace(String str, String symbol, String replace) {
    StringBuilder sb = new StringBuilder();
    int i = 0;
    int l = 0;
    int k;

    for (int j = 0; (k = str.indexOf(symbol, j)) >= 0; j = k + 1) {
      l += countQuotes(str, j, k);

      if ((k > 0) && (str.charAt(k - 1) != '\\') && (l % 2) == 0) {
        sb.append(trim(str.substring(i, k)) + replace);

        i = k + 1;
        l = 0;
      }
    }

    sb.append(trim(str.substring(i)));

    return sb.toString();
  }

  /**
   * Returns the number of Quotation from i to j
   *
   * @return number of quotes
   */
  private static int countQuotes(String s, int i, int j) {
    int k = 0;

    for (int l = i; l < j; l++) {
      if (s.charAt(l) == '"') {
        k++;
      }
    }

    return k;
  }

  //only for the end of a space character occurring at the end of the string from rfc2253

  /**
   * Method trim
   *
   * @return the string
   */
  static String trim(String str) {

    String trimed = str.trim();
    int i = str.indexOf(trimed) + trimed.length();

    if ((str.length() > i) && trimed.endsWith("\\")
        && !trimed.endsWith("\\\\") && (str.charAt(i) == ' ')) {
      trimed = trimed + " ";
    }

    return trimed;
  }

}
